#pragma once

#include "DataTypes.h"
#include <string>
#include <filesystem>
#include <fstream>
#include <vector>
#include <map>
#include <algorithm>
#include <optional>
#include <stdexcept>
#include <type_traits>

namespace FileIO {
    bool loadGraph(const std::string& nodes_path, const std::string& edges_path, Graph& graph, CodeMap& code_map);
    std::vector<int> loadScheduleFromFile(const std::string& filename);

    std::vector<std::pair<int, long long>> formatSpillLogForOutput(
        const std::unordered_map<int, long long>& spill_map, const Graph& graph);

    // --- Template functions and utility functions ---
    
    inline std::string trim(const std::string& s) {
        size_t first = s.find_first_not_of(" \t\n\v\f\r");
        if (std::string::npos == first) {
            return "";
        }
        size_t last = s.find_last_not_of(" \t\n\v\f\r");
        return s.substr(first, (last - first + 1));
    }

    template<typename T>
    inline std::optional<T> safe_string_to_num(const std::string& s) {
        std::string trimmed_s = trim(s);
        if (trimmed_s.empty()) {
            return std::nullopt;
        }
        try {
            if constexpr (std::is_same_v<T, long long>) {
                return std::stoll(trimmed_s);
            }
            else if constexpr (std::is_same_v<T, int>) {
                return std::stoi(trimmed_s);
            }
        }
        catch (const std::invalid_argument&) {
            return std::nullopt;
        }
        catch (const std::out_of_range&) {
            return std::nullopt;
        }
        return std::nullopt;
    }

    // --- Implementation moved to header to resolve linking issues ---

    inline void writeScheduleToFile(const std::filesystem::path& filename, const std::vector<int>& schedule) {
        std::ofstream file(filename);
        if (!file.is_open()) {
            throw std::runtime_error("Error: Cannot write to schedule file: " + filename.string());
        }
        for (int id : schedule) {
            file << (id - 1) << "\n";
        }
    }

    inline void writeMemoryToFile(const std::filesystem::path& filename, const std::unordered_map<int, long long>& offsets_map) {
        std::ofstream file(filename);
        if (!file.is_open()) {
            throw std::runtime_error("Error: Cannot write to memory file: " + filename.string());
        }

        if (offsets_map.empty()) return;

        std::map<int, long long> sorted_map(offsets_map.begin(), offsets_map.end());

        bool first = true;
        for (const auto& pair : sorted_map) {
            if (!first) file << "\n";
            file << pair.first << ":" << pair.second;
            first = false;
        }
    }

    inline void writeSpillToFile(const std::filesystem::path& filename, const std::vector<std::pair<int, long long>>& spill_log) {
        std::ofstream file(filename);
        if (!file.is_open()) {
            throw std::runtime_error("Error: Cannot write to spill file: " + filename.string());
        }

        if (spill_log.empty()) return;

        bool first = true;
        for (const auto& entry : spill_log) {
            if (!first) file << "\n";
            file << entry.first << ":" << entry.second;
            first = false;
        }
    }
}